<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <button id="btn1">外反锁</button>
    <button id="btn2">关灯</button>

    <!-- 
    ·全局共有5件小米智能家居：小米控制台、小米门禁、小米电灯、小米电视；
    ·从外反锁门=>全屋进入【外出模式】=>所有电器自动断电
    ·电灯关闭10分钟=>全屋自动进入【睡眠模式】=>电视关闭+空调进入微风状态+门禁内反锁；
    -->

    <!-- 设计模式底层架构 -->
    <script>
      /* 小米家居基类 */
      class MiDevice {
        constructor(name) {
          this.name = name;
        }

        powerOn() {
          console.log(this.name, "powerOn");
        }

        powerOff() {
          console.log(this.name, "powerOff");
        }
      }

      /* 事件源=被观察者（Observable） */
      class DataSource extends MiDevice {
        constructor(name) {
          super(name);
          this.listeners = [];
          this.value = null;
        }

        // 添加观察者
        addListener(...listeners) {
          this.listeners.push(...listeners);
        }

        // 注销观察者
        removeListener(listener) {
          this.listeners = this.listeners.filter((li) => li !== listener);
        }

        // 发布事件（通知所有观察者响应）
        publishEvent(event) {
          this.listeners.forEach((li) => li.onEvent(event));
        }
      }

      /* 观察者：只要有onEvent(event)方法就是观察者（无论是否显式地继承Observer） */
      class Observer extends MiDevice {
        onEvent(event) {
          console.log(this.name, "onEvent", event);
        }
      }

      /* 组合 */
      class Compose extends MiDevice {
        constructor(name) {
          super(name);
          this.components = [];
        }

        // 添加组件
        addComponent(component) {
          this.components.push(component);
        }

        // 注销组件
        removeComponent(component) {
          for (let i = 0; i < this.components.length; i++) {
            if (this.components[i] === component) {
              this.components.splice(i, 1);
              break;
            }
          }
        }

        // 下达号令
        platformSwitchMode(mode) {
          // 让所有组件都响应号令
          this.components.forEach((c) => c.switchMode(mode));
        }
      }

      /* 组件: 只要有switchMode(mode)方法就可算是组件（无论是否显式地继承Component） */
      class Component extends MiDevice {
        switchMode(mode) {
          console.log(this.name, "switchMode", mode);
        }
      }
    </script>

    <!-- 家居类封装 -->
    <script>
      /* 小爱同学 */
      class MiControl extends /* Observer */ Compose {
        static mode = {
          modeAbsent: "外出模式",
          modeSleep: "睡眠模式",
        };

        static singleton = null;

        static getSingleton() {
          !this.singleton && (this.singleton = new MiControl());
          return this.singleton;
        }

        constructor() {
          super("小爱同学");
        }

        /* 作为观察者响应事件 */
        /* 我已经有onEvent了 所以我已经是观察者了 无需一定要继承Observer */
        onEvent(event) {
          // super.onEvent(event)
          console.log(this.name, "onEvent", event);

          switch (event.type) {
            /* 处理门禁事件 */
            case MiDoor.eventType:
              if (event.value === MiDoor.mode.lockFromOutside) {
                // 命令所有组件进入【外出模式】
                this.platformSwitchMode(MiControl.mode.modeAbsent);
              }
              break;

            /* 处理灯具事件 */
            case MiLight.eventType:
              if (event.value === MiLight.mode.sleep) {
                // 命令所有组件进入【外出模式】
                this.platformSwitchMode(MiControl.mode.modeSleep);
              }
              break;

            default:
              break;
          }
        }
      }

      class MiDoor extends DataSource {
        static eventType = "MiDoor";

        static mode = {
          open: "open",
          closed: "closed",
          lockFromInside: "lockFromInside",
          lockFromOutside: "lockFromOutside",
        };

        constructor() {
          super("小米门禁");
        }

        handle(mode) {
          console.log(this.name, mode);

          /* 作为事件源主动发布事件 */
          this.publishEvent({
            type: MiDoor.eventType,
            value: mode,
          });
        }

        switchMode(mode) {
          console.log(this.name, "switchMode", mode);
          switch (mode) {
            case MiControl.mode.modeSleep:
              this.handle(MiDoor.mode.lockFromInside);
              break;

            default:
              break;
          }
        }

      }

      class MiLight extends /* Component */ DataSource {
        static eventType = "MiLight";

        static mode = {
          sleep: "睡眠模式",
          ktv: "劲舞模式",
          candleMeal: "烛光晚餐模式",
        };

        constructor() {
          super("MiLight");
        }

        switchMode(mode) {
          switch (mode) {
            // 外出模式
            case MiControl.mode.modeAbsent:
              this.powerOff();
              break;
            default:
              break;
          }
        }

        powerOff() {
          super.powerOff();
          setTimeout(() => {
            this.publishEvent({
              type: MiLight.eventType,
              value: MiLight.mode.sleep,
            });
          }, 3000);
        }
      }

      class MiTV extends Component {
        constructor() {
          super("MiTV");
        }

        switchMode(mode) {
          switch (mode) {
            // 外出模式
            case MiControl.mode.modeAbsent:
              this.powerOff();
              break;

            // 睡眠模式
            case MiControl.mode.modeSleep:
              this.sleep();
              break;
            default:
              break;
          }
        }

        sleep() {
          console.log(this.name, "正在静默下载您喜爱的老师的片子...");
        }
      }
    </script>

    <!-- 业务逻辑 -->
    <script>
      const mcontrol = MiControl.getSingleton();

      const mdoor = new MiDoor();
      const mlight = new MiLight();
      const mtv = new MiTV();

      // 小米门禁（事件源）添加小爱同学作为观察者
      mdoor.addListener(mcontrol);
      mlight.addListener(mcontrol);

      /* 所有设备都归小爱同学统一号令 */
      mcontrol.addComponent(mdoor);
      mcontrol.addComponent(mlight);
      mcontrol.addComponent(mtv);

      /* 反锁门 */
      btn1.onclick = function (e) {
        mdoor.handle(MiDoor.mode.lockFromOutside);
      };

      /* 关灯 */
      btn2.onclick = function (e) {
        mlight.powerOff();
      };
    </script>
    
  </body>
</html>
